﻿namespace MefContrib.Models.Provider
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition.Primitives;
    using System.ComponentModel.Composition.Hosting;
    using System.Globalization;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using MefContrib.Models.Provider.Validation;

    /// <summary>
    /// 
    /// </summary>
    public class ProviderImportDefinition : ImportDefinition
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ProviderImportDefinition"/> class, using
        /// the provided initial values.
        /// </summary>
        /// <param name="member"></param>
        /// <param name="importDescription"></param>
        public ProviderImportDefinition(MemberInfo member, ImportDescription importDescription)
            : base(
            GetConstraint(CompositionServices.GetContractNameFromImportDescription(member, importDescription), importDescription.RequiredMetadata),
            CompositionServices.GetContractNameFromImportDescription(member, importDescription),
            GetCardinality(member, importDescription.AllowDefault),
            importDescription.Recomposable, importDescription.Prerequisite)
        {
            
            member
                .Require("member")
                .NotNull();

            importDescription
                .Require("importDescription")
                .NotNull();

            this.AllowDefault = importDescription.AllowDefault;
            this.Member = member;
            this.RequiredMetadata = importDescription.RequiredMetadata ?? Enumerable.Empty<string>();

            // TOOD: Refactor

            ComposableMember composable =
                this.Member.ToComposableMember();

            Type lazy = composable.ToLazyType();
            if (composable.IsLazyType)
            {
                Type[] arguments = 
                    lazy.GetGenericArguments();

                if( arguments[1] != typeof(IDictionary<string, object>))
                {
                    IEnumerable<string> test =
                        from property in arguments[1].GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                        select property.Name;

                    this.RequiredMetadata = this.RequiredMetadata.Concat(test);
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the property or field will be set 
        /// to its type's default value when an export with the contract name is not present in 
        /// the container.
        /// </summary>
        /// <value>
        /// <see langword="true"/> if the property, field or parameter will be set 
        /// its type's default value when an export with the <see cref="ContractName"/> is not 
        /// present in the <see cref="CompositionContainer"/>; otherwise, <see langword="false"/>. 
        /// The default value is <see langword="false"/>.
        /// </value>
        public bool AllowDefault { get; protected set; }

        /// <summary>
        /// Gets an expression that defines conditions that must be matched for the import 
        /// described by the import definition to be satisfied.
        /// </summary>
        /// <returns>
        /// A <see cref="Expression"/> containing a <see cref="Func{T, TResult}"/> 
        /// that defines the conditions that must be matched for the 
        /// <see cref="ImportDefinition"/> to be satisfied by an <see cref="Export"/>.
        /// </returns>
        private static Expression<Func<ExportDefinition, bool>> GetConstraint(string contractName, IEnumerable<string> requiredMetadata)
        {
                //this.constraint = (exportDefinition =>
                //    exportDefinition.ContractName == this.ContractName && this.RequiredMetadata.All(m => exportDefinition.Metadata.ContainsKey(m)));

                // HACK: EXTREMLY TEMPORARY WORKAROUND!

                string x = contractName;
                IEnumerable<string> y = requiredMetadata;

                Expression<Func<ExportDefinition,bool>> constraint = exportDefinition =>
                    exportDefinition.ContractName == x && y.All(m => exportDefinition.Metadata.ContainsKey(m));

                return constraint;
        }

        private static ImportCardinality GetCardinality(MemberInfo member, bool allowDefault)
        {
            return (member == null) ? ImportCardinality.ZeroOrMore : member.GetCardinality(allowDefault);
        }

        /// <summary>
        /// Gets the member that the import is defined for.
        /// </summary>
        /// <value>A <see cref="MemberInfo"/> object.</value>
        public MemberInfo Member { get; protected set; }

        /// <summary>
        /// Gets the metadata names of the export required by the import definition.
        /// </summary>
        /// <value>
        /// An <see cref="IEnumerable{T}"/> of <see cref="String"/> objects containing
        /// the metadata names of the <see cref="Export"/> required by the 
        /// <see cref="ProviderImportDefinition"/>. The default is an empty 
        /// <see cref="IEnumerable{T}"/>.
        /// </value>
        public IEnumerable<string> RequiredMetadata { get; protected set; }

        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture,
                                 "{0} (ContractName=\"{1}\")", Member.Name, ContractName);
        }
    }
}